#include "UBORenderFeature.h"
#include <SERender/Resourses/Loader/UniformBufferLoader.h>
#include <SERender/Entity/Camera.h>
#include <SERender/Renderer/Renderer.h>
#include <SERender/Descriptors/FrameDescriptor.h>
#include <SEngine/Render/Descriptors/SceneDescriptor.h>
#include <SECore/Ecs/Scene.h>
#include <SEngine/Ecs/Components/LightComp.h>
#include <chrono>
#include <SEDebug/Log/Log.h>

namespace SEngine
{
    std::unique_ptr<UniformBuffer> UBORenderFeature::m_engineUBO;
    std::unique_ptr<UniformBuffer> UBORenderFeature::m_lightUBO;

    void EngineUniformBlockBinder::Bind(Shader & shader)
    {
        UniformBuffer::BindToShader(shader, "EngineUBO", 0);
        UniformBuffer::BindToShader(shader, "LightUBO", 1);
    }


    UBORenderFeature::UBORenderFeature(Renderer &renderer)
        : RenderFeature(renderer) 
    {
        if (!m_engineUBO) {
            UniformBufferLoader loader;
            m_engineUBO = loader.AddBlock("u_view", sizeof(glm::mat4))
                                .AddBlock("u_project", sizeof(glm::mat4))
                                .AddBlock("u_camPos", sizeof(glm::vec3))
                                .AddBlock("u_camInfo", sizeof(glm::vec3))
                                .AddBlock("u_inversePV", sizeof(glm::mat4))
                                .AddBlock("u_time", sizeof(float))
                                .CreateUniformBuffer(std::make_unique<CurrentDrive>(), "EngineUBO", 0);
        }
        if (!m_lightUBO) {
            UniformBufferLoader loader;
            for (int i = 0; i < 64; ++i) {
                loader.AddBlock("u_light" + std::to_string(i), sizeof(glm::mat4));
            }
            loader.AddBlock("u_lightNum", sizeof(int));
            m_lightUBO = loader.CreateUniformBuffer(std::make_unique<CurrentDrive>(), "LightUBO", 1);
        }
    }

    void UBORenderFeature::BeginFrame()
    {
        auto frameDescriptor = m_renderer.AnyDescriptor::GetDescriptor<FrameDescriptor>();
        if (!frameDescriptor) {
            return;
        }
        UpdateCameraUBOData(frameDescriptor->get().camera, frameDescriptor->get().w, frameDescriptor->get().h);
        static double baseTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count() / 1000.0;
        double milliseconds_since_epoch = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count() / 1000.0;
        m_engineUBO->SetSubData("u_time", (float)(milliseconds_since_epoch - baseTime));
        m_engineUBO->Bind();

        auto sceneDes = m_renderer.AnyDescriptor::GetDescriptor<SceneDescriptor>();
        if (!sceneDes || !sceneDes->get().scene) {
            return;
        }
        int lightNum = 0;
        sceneDes->get().scene->ComponentsEx([this, &lightNum](Actor & actor, auto & comp) {
            m_lightUBO->SetSubData("u_light" + std::to_string(lightNum), comp.light.Packet());
            lightNum++;
        }, LightComp());
        m_lightUBO->SetSubData("u_lightNum", lightNum);
        m_lightUBO->Bind();
    }

    void UBORenderFeature::EndFrame()
    {
        m_engineUBO->Unbind();
    }

    void UBORenderFeature::UpdateCameraUBOData(Camera &camera, int width, int height)
    {
        glm::mat4 v = camera.GetViewMatrix();
        glm::mat4 p = camera.GetProjectMatrix(width, height);
        m_engineUBO->SetSubData("u_view", v);
        m_engineUBO->SetSubData("u_project", p);
        m_engineUBO->SetSubData("u_camPos", camera.GetPos());
        m_engineUBO->SetSubData("u_camInfo", 
            glm::vec3{
                camera.GetType(),
                camera.GetZNear(),
                camera.GetZFar()
            }
        );
        m_engineUBO->SetSubData("u_inversePV", glm::inverse(p * v));
    }

} // namespace SEngine

